Skip to content

feat(shopping): add Fee Extension for checkout and cart (Option B)#245

Open
maximenajim wants to merge 5 commits intomainfrom
feat/fee-extension
Open

feat(shopping): add Fee Extension for checkout and cart (Option B)#245
maximenajim wants to merge 5 commits intomainfrom
feat/fee-extension

Conversation

@maximenajim
Copy link

@maximenajim maximenajim commented Mar 9, 2026

Summary

Implements the Fee Extension (dev.ucp.shopping.fee) per Issue #219 (Option B), enabling businesses to surface itemized fees on checkout sessions and carts.

Key design decisions

  • Shared allocation type: Extracts types/allocation.json as a reusable type referenced by both discount and fee extensions — avoids cross-extension $ref coupling between fee and discount schemas
  • fee_type required: Fees without a type are ambiguous for platform rendering/categorization
  • exclusiveMinimum: 0: Zero-amount fees are not permitted — waived fees are omitted from the array entirely
  • additionalProperties: false on fee type for strict validation
  • readOnly: true on fees.applied for generic tooling/codegen compatibility
  • $ref to amount.json on all monetary fields to preserve the canonical amount type chain
  • RFC 9535 JSONPath dialect explicitly named in allocation path descriptions
  • Conditional language in total.json descriptions ("When the Fee Extension is present…")

Files changed (8)

New (4):

  • source/schemas/shopping/types/allocation.json — Shared allocation type
  • source/schemas/shopping/types/fee.json — Individual fee schema
  • source/schemas/shopping/fee.json — Fee Extension schema (checkout + cart)
  • docs/specification/fee.md — Full specification page

Modified (4):

  • source/schemas/shopping/discount.json — Allocation now $refs shared type
  • source/schemas/shopping/types/total.json — Conditional fee language in descriptions
  • mkdocs.yml — Nav + llmstxt entries
  • .cspell/custom-words.txt — New terms

Spec highlights

  • Mismatch handling: platforms SHOULD surface discrepancy; MAY use continue_url for resolution
  • Receiver rejection: business receivers MUST reject (not ignore) client-supplied fee fields
  • Sanitization: title/description/display_text are plain text — renderers MUST escape
  • Waived fees: omitted from array (not zero-amount entries); businesses MAY use messages[] to communicate
  • Error code: readonly_field_not_allowed added for fee field rejection
  • Discovery: uses multi-parent extends array form per overview spec

Closes #219
Related: #220

@google-cla
Copy link

google-cla bot commented Mar 9, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Implement structured fee support via dev.ucp.shopping.fee extension, enabling businesses to surface itemized fees (service, handling, recycling, etc.) with allocation breakdowns, taxability, and waivability metadata. Extracts shared allocation type to decouple fee and discount extensions. Closes #219.
@maximenajim maximenajim added the TC review Ready for TC review label Mar 9, 2026
Comment on lines +67 to +70
!!! note "Partial adoption"
A business MAY support fees on checkout only, cart only, or both. Each
`extends` entry is independent. Platforms should check which base
capabilities the fee extension extends before expecting `fees` in responses.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious if this needs to be in docs/specification/overview.md, as this should be true for all extensions, correct?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — partial adoption is indeed a general extension pattern. I'll open a follow-up to add guidance in overview.md about how platforms should handle extensions that declare multiple extends entries. For now the note stays here so this spec is self-contained, but the broader principle should live in the overview.

Comment on lines +97 to +105
| Total Type | Description |
| ---------------- | ------------------------------------------------------------- |
| `subtotal` | Sum of line item prices before any adjustments |
| `items_discount` | Discounts allocated to line items |
| `discount` | Order-level discounts (shipping, flat amount) |
| `fulfillment` | Shipping, delivery, or pickup costs |
| `tax` | Tax amount |
| `fee` | **Single aggregated fee total** — sum of all `fees.applied[]` |
| `total` | Grand total: `subtotal - discount + fulfillment + tax + fee` |
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this table be here under docs/specification/fee.md or should fee.md just call out the new total type? I ask because it could make the docs harder to maintain if every extension that affects totals recreates the totals table (e.g. Discount also could have this table).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair concern about maintenance. I kept the table here so the fee spec is self-contained for readers — but you're right that if every extension duplicates it, drift is inevitable. For now I'll keep it with a note that the canonical source is the checkout/totals spec. We could consider a shared include or a cross-reference pattern in a follow-up.

| `discount` | Order-level discounts (shipping, flat amount) |
| `fulfillment` | Shipping, delivery, or pickup costs |
| `tax` | Tax amount |
| `fee` | **Single aggregated fee total** — sum of all `fees.applied[]` |
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This language is helpful, and we should consider being this crisp with the other total types.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — agreed, we should bring this level of precision to the other total types as well. Happy to help with that in a follow-up pass across the spec.

Businesses MUST ensure this invariant holds. If a platform detects a mismatch
between the aggregated fee total and the sum of itemized fees, the platform
SHOULD treat the response as invalid and MUST NOT complete the checkout without
surfacing the discrepancy to the user.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if the discrepancy is surfaced, is it appropriate for the platform to proceed? Should the Platform use the continue_url is such a case? If yes, should the continue_url be a required field? Currently, it doesn't seem like we have a good way for platforms to handle these cases.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great question. Updated in d03ce30 — softened the language from "MUST NOT complete" to "SHOULD surface the discrepancy" and explicitly added continue_url as a fallback option:

Platforms MAY use continue_url to hand off to the business UI for resolution rather than attempting to complete the checkout with inconsistent fee data.

I didn't make continue_url required since it's already an optional field on checkout — making it required just for fee mismatch cases would be a broader spec change. But the guidance now gives platforms a clear escape hatch.

SHOULD treat the response as invalid and MUST NOT complete the checkout without
surfacing the discrepancy to the user.

!!! note "When the Fee Extension is absent"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still feels a bit ambiguous. Does this note deserve the same normative language as the Invariant above? That is, "totals[type=fee] MUST represent the aggregated fee total"; or alternatively, something about there MAY be n number of totals[type=fee] objects in totals (the question that sparked issue #219 ).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in d03ce30 — the admonition now uses explicit normative language:

When the Fee Extension is present, there MUST be at most one totals[] entry with type: "fee", and its amount MUST equal sum(fees.applied[].amount). When the Fee Extension is not advertised, the interpretation of any totals[type=fee] entry is business-defined — platforms SHOULD render it using display_text but MUST NOT assume itemized fee data is available.

This addresses both the "how many fee totals?" question from #219 and the absent-extension semantics.

},
"amount": {
"$ref": "amount.json",
"description": "Amount allocated to this target in ISO 4217 minor units."
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should adopt this across the spec. In other places we are simply saying "minor units (cents)" which is inadequate for all currencies.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — "minor units (cents)" is USD-centric and inadequate. Happy to help with a follow-up sweep to adopt consistent ISO 4217 language across all monetary fields in the spec.

"properties": {
"id": {
"type": "string",
"description": "Unique fee identifier. Unlike applied discounts, fees always require an id to enable stable referencing across updates."
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are fee ids expected to be consistent across checkout sessions? Or just across requests within a checkout session or cart?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Within a session only. Updated in d03ce30 — both the schema description and the docs now clarify:

Fee IDs are scoped to a single checkout or cart session. The same fee retains its id across requests within a session (create → update → complete), but the id is not guaranteed to be consistent across separate sessions. Businesses control ID generation.

This gives platforms stable tracking within a session (which is the primary use case for UI diffing) without imposing a cross-session uniqueness burden on businesses.

},
"description": {
"type": "string",
"description": "Optional explanation of why the fee is charged."
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also human-readable, I assume. As with conversations around disclosure, I wonder how Platforms should navigate Businesses potentially wanting richer text, or even images.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in d03ce30 — the description field is now explicitly marked as plain text in both the schema and the spec. For richer content needs, the spec now includes a forward reference:

The description field is plain text — for richer content such as regulatory disclosures, images, or formatted copy, businesses should use the Disclosures capability (see #222) when available.

This keeps the fee schema simple while pointing to the right extension point for richer content.

Comment on lines +129 to +136
| `service` | General service fee for order processing | Platform service fee |
| `handling` | Physical handling and packaging of goods | Oversized item handling fee |
| `recycling` | Disposal or recycling of materials | Electronics recycling fee |
| `processing` | Payment or order processing surcharge | Credit card processing fee |
| `regulatory` | Government-mandated fee or compliance charge | Mattress recycling surcharge |
| `convenience` | Fee for using a particular ordering channel | Online ordering convenience fee |
| `restocking` | Fee for processing returns or exchanges | Return restocking fee |
| `environmental` | Environmental impact or sustainability surcharge | Carbon offset fee |
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the value of this spec defining types, especially so many of them? Does a Platform need to know the various types?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The well-known types serve interoperability — they let platforms build smarter UX when they recognize a type (e.g., grouping all recycling fees, or showing a specific icon for service fees). But they're explicitly not prescriptive: fee_type is an open string, unknown values are fine, and platforms SHOULD fall back to displaying the title.

Think of it like HTTP status codes — the well-known ones enable richer behavior, but the system works with unknown ones too. The table documents common patterns we've seen across commerce platforms to reduce the "what should I call this?" guesswork.


3. **Positive amounts only:** All fee amounts use `exclusiveMinimum: 0` —
zero-amount fees are not permitted. If a fee does not apply, it MUST be
omitted from the `applied` array entirely. This includes waived fees: when a
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there perhaps a need to communicate to the Platform / User that a fee was waived? If we do not allow zero-amount fees, how should this be communicated in the response?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great question. Updated in d03ce30 — added guidance for using messages[] to communicate waived fees:

{
  "messages": [
    {
      "type": "info",
      "code": "fee_waived",
      "content": "Service Fee waived for loyalty members."
    }
  ]
}

This uses the existing messages mechanism rather than inventing a fee-specific signaling pattern. The code: "fee_waived" gives platforms something to key on programmatically, and the content provides the human-readable explanation.

operations, meaning platforms never send fee data in requests — fees are
determined entirely by the business and returned in responses.

Business receivers MUST reject any `fees` fields provided by platforms in
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we add an error code to source/schemas/shopping/types/error_code.json for this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d03ce30 — added readonly_field_not_allowed to source/schemas/shopping/types/error_code.json examples, and referenced it in the fee spec:

Businesses SHOULD use the readonly_field_not_allowed error code when rejecting such requests.

This follows the existing pattern where error_code.json uses examples (not enum) for well-known codes.

"amount": 500,
"fee_type": "recycling",
"taxable": true,
"description": "State-mandated electronics recycling fee."
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This made me think of #222 . Is a plain-text description adequate, or are there fees that may need to be more expressive; perhaps we handle those with disclosures?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly — same thought. Updated in d03ce30 to add a forward reference to #222:

The description field is plain text — for richer content such as regulatory disclosures, images, or formatted copy, businesses should use the Disclosures capability (see #222) when available.

Plain text covers the 80% case (simple one-liner like "State-mandated recycling fee"). For the 20% that needs formatted copy, images, or regulatory legalese, Disclosures is the right extension point. Keeping description as plain text avoids the sanitization complexity of allowing HTML/markdown in every fee object.

- Soften mismatch language; add continue_url as fallback option (#4)
- Strengthen normative admonition with MUST/MUST NOT language (#5)
- Scope fee IDs to session; clarify in schema description and docs (#7)
- Mark description as plain text; reference Disclosures #222 (#8/#12)
- Add messages[] guidance with fee_waived example for waived fees (#10)
- Add readonly_field_not_allowed error code; reference in fee spec (#11)
maximenajim added a commit that referenced this pull request Mar 10, 2026
- Soften mismatch language; add continue_url as fallback option (#4)
- Strengthen normative admonition with MUST/MUST NOT language (#5)
- Scope fee IDs to session; clarify in schema description and docs (#7)
- Mark description as plain text; reference Disclosures #222 (#8/#12)
- Add messages[] guidance with fee_waived example for waived fees (#10)
- Add readonly_field_not_allowed error code; reference in fee spec (#11)

Co-authored-by: Dayton <31824+SVDEA001@users.noreply.git.target.com>
…to admonition

- Use multi-parent extends array per overview spec (#multi-parent-extensions)
- Convert trailing blockquote to admonition for consistency with rest of doc
@igrigorik igrigorik added this to the Working Draft milestone Mar 10, 2026
Copy link

@ACSchil ACSchil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

```

!!! note "Partial adoption"
A business MAY support fees on checkout only, cart only, or both. When

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: While the current language allows for independent extension of cart and checkout, we should still guide implementors to ensure a stable state machine and avoid 'price surprises.' Late-stage disclosure is particularly disruptive for platform budget validation and agentic flows.

I suggest adding a perspective on the expected merchant/customer experience to maximize transparency:

  1. Early Disclosure: A business SHOULD include fees at the cart level whenever the fee criteria (e.g., item types or subtotal thresholds) are known. Fees SHOULD NOT be deferred to checkout unless they are strictly dependent on data only available at that stage (e.g., payment method surcharges).

  2. Disclosure Continuity: A business SHOULD include fees at checkout if they were provided at the cart level to ensure continuity of disclosure granularity and avoid a 'lossy' state transition.

This helps ensures that the merchant-controlled portion of the cost is deterministic as early as possible and remains consistent throughout the transaction lifecycle.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion — this has been addressed in 47b815d. Added a new "Fee disclosure timing" admonition right after the "Partial adoption" note with both of your recommended guidelines:

  1. Early disclosure: fees SHOULD be surfaced at cart level when criteria are known; SHOULD NOT be deferred to checkout unless dependent on checkout-only data.
  2. Disclosure continuity: fees provided at cart level SHOULD carry through to checkout to avoid a lossy state transition.

Uses SHOULD-level normative language to keep it guidance rather than a hard requirement.

Add 'Fee disclosure timing' admonition to fee spec with two SHOULD-level
guidelines: early disclosure of fees at cart level when criteria are known,
and disclosure continuity from cart to checkout to avoid lossy state
transitions. Addresses PR #245 review feedback from gsmith85.
Businesses MUST ensure this invariant holds. If a platform detects a mismatch
between the aggregated fee total and the sum of itemized fees, the platform
SHOULD treat the response as potentially invalid and SHOULD surface the
discrepancy to the user. Platforms MAY use `continue_url` to hand off to the
Copy link

@gsmith85 gsmith85 Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: If sum(fees.applied[].amount) < totals[type=fee].amount escalate. Otherwise, this could put the user in a position where they're paying unspecified fees through an intermediary (the platform) which complicates dispute resolution.

Relatedly, has the TC considered strategies to ensure that the totals in the checkout object itself are consistent (i.e. follow the math you have documented above?) .

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — updated in 81ea0fa. The mismatch language now distinguishes between the two directions:

  • Aggregated total exceeds itemized sum: platform MUST treat as an error and MUST NOT complete checkout — the user would otherwise pay unspecified fees through an intermediary, complicating dispute resolution. Platforms SHOULD use continue_url for resolution.
  • Aggregated total less than itemized sum: platform SHOULD surface the discrepancy but MAY proceed, since the user is not being overcharged.

Regarding TC strategies for totals consistency validation — that's a great broader question. The invariant enforcement here is fee-specific, but a general totals consistency check (ensuring the math holds across all total types) would be a valuable follow-up discussion for the TC. Happy to open an issue for that.

| `processing` | Payment or order processing surcharge | Credit card processing fee |
| `regulatory` | Government-mandated fee or compliance charge | Mattress recycling surcharge |
| `convenience` | Fee for using a particular ordering channel | Online ordering convenience fee |
| `restocking` | Fee for processing returns or exchanges | Return restocking fee |

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: restocking is a post-purchase/reverse-logistics event. Omit it for the time being?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — removed in 81ea0fa. Restocking is a post-purchase/reverse-logistics event and doesn't belong in a checkout/cart fee context. It can always be added back later if a returns-specific extension needs it.

"total"
],
"description": "Type of total categorization.",
"description": "Type of total categorization. When the Fee Extension is present, businesses SHOULD include a single aggregated entry with type 'fee' whose amount equals the sum of all fees in the Fee Extension's applied array.",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Observation: The lack of schema level enforcement here and the one-off carve out for discounts feed into a strong preference for Approach A (#219).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appreciate the observation. The tension between schema-level enforcement and extension flexibility is real — the current approach (Option B) trades stricter schema coupling for independent extensibility, which was the TC's preference for this iteration. That said, the concern about one-off carve-outs is valid and worth revisiting as more extensions land. If the pattern becomes unwieldy, migrating toward Approach A (or a hybrid) could be a future discussion. Happy to capture this as a tracked consideration if helpful.

limitations under the License.
-->

# Fee Extension
Copy link

@gsmith85 gsmith85 Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternative: Polymorphic Composition

We can use JSON Schema's allOf to create a FeeTotal that extends the base Total object which achieves the goal of providing fees structured metadata (per: #219). This allows us to keep a unified totals[] array while providing the "slots" for metadata only when type == fee.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://ucp.dev/schemas/shopping/types/fee_total.json",
  "title": "FeeTotal",
  "allOf": [
    { "$ref": "total.json" },
    {
      "type": "object",
      "properties": {
        "type": {
          "const": "fee"
        },
        "description": {
          "type": "string",
          "description": "Optional plain-text explanation of why the fee is charged. For richer content (e.g., regulatory disclosures), see the Disclosures capability."
        },
        "fee_type": {
          "type": "string",
          "description": "Type of fee (open string). Well-known values: service, handling, recycling, processing, regulatory, convenience, restocking, environmental. Platforms SHOULD handle unknown values gracefully by displaying the fee title to the user."
        },
        "taxable": {
          "type": "boolean",
          "default": false,
          "description": "Whether this fee is subject to tax."
        }
      }
    }
  ]
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting alternative — the polymorphic FeeTotal via allOf composition is a clean pattern. It would keep the unified totals[] array while adding structured metadata slots only when type == fee, which addresses the core goal from #219.

The tradeoff vs. the current approach: it moves fee metadata into the totals array (closer to where the aggregated amount lives) rather than a separate fees extension object. This simplifies the schema graph but means platforms need to handle polymorphic totals entries — some with just type/amount/display_text, others with additional fee-specific fields.

I think this is worth a deeper TC discussion as a potential evolution, especially if other extensions (e.g., fulfillment surcharges) would benefit from the same pattern. Want to open a discussion issue to explore this alongside the Approach A consideration?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, Maxime -- appreciate the receptiveness to the alternative. I agree that the tradeoff on polymorphic handling is the right pivot to discuss, especially if we can use it to standardize how 'why' metadata is attached to the ledger across other extensions (discounts, fulfillment, etc.).

Per your suggestion, I’ve opened a Discussion issue here to dive into the 'Unified Ledger' pattern: #258

"amount": 1500,
"fee_type": "handling",
"allocations": [
{ "path": "$.fulfillment.options[0]", "amount": 1500 }
Copy link

@gsmith85 gsmith85 Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The waivable flag was a bit of an "aha" moment for me that leaves me oppositional to Plan B. It highlights why separating discounts and fees may be a mistake: in any robust POS or financial model, these are simply adjustments to a base price.

The problem with the aggregate-fee with omissions scheme is that merchants (e.g. DoorDash) don't just hide a waived (delivery) fees, they often display the fee as a line item followed by the corresponding credit. This debit-credit pair serves two purposes:

  1. Value Signaling: It explicitly proves the discount was applied and reinforces the service's value.
  2. Audit Integrity: It maintains a mathematically sound record rather than forcing the platform to guess why a field disappeared or how a taxable fee impacted the final sum.

We should aspire to a totals ledger that is governed by a simple deterministic invariant: total = Subtotal + Sum(Adjustments).

I believe the alternative (#245 (comment)) suggested above may allow you to address the "No structured metadata" limitation of Option A, while avoiding the loose contracts of Option B.

…fee types

- Strengthen fee mismatch invariant with directional escalation: aggregated
  total exceeding itemized sum MUST be treated as an error (unspecified fees
  through intermediary); less-than case MAY proceed with caution
- Remove restocking from well-known fee types (post-purchase event, not
  applicable to checkout/cart context)

Addresses PR #245 review feedback from gsmith85.
@igrigorik
Copy link
Contributor

@maximenajim @gsmith85 great discussion in the comments. I started typing review replies but then found myself cross-linking N-ways into sub-threads and finally hitting a 🤦🏻🤦🏻🤦🏻 wall with the extension route -- which, coming in into this review, was my leading contender.

Curious to hear your thoughts on this: #261

It's in the intro of the draft, but the key thing that "broke" extension route for me was the mandatory display vs optional nature of extensions. I initially went to draft the mandatory N=1 in totals, but then realized that this would make extensions required to conduct certain shapes of commerce.. which is an unnecessary complication we can avoid because it's a pure presentation concern, not a negotiation requirement. See the rest in the draft above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

TC review Ready for TC review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Clarify itemized fee support in totals[]

4 participants